from django.db import models
from django.utils.translation import ugettext_lazy as _
from opinion_extractor.utils.models import get_childs_own_fields
import exceptions
import managers


class TemporalModel(models.Model):
    # Minimum days required to pass between different versions
    min_days_between_versions = None
    # List of fields which will never change between versions (used for lookup)
    unchangeable_fields = None
    # List of fields that are compared between versions to check if the temporal
    # object has been changed. Will be automatically discovered if None
    changeable_fields = None
    
    valid_from = models.DateField(
        _('valid from date'),
        auto_now_add=True,
        db_index=True,
    )
    valid_to = models.DateField(
        _('valid to date'),
        blank=True,
        db_index=True,
        editable=False,
        null=True
    )
    
    objects = managers.TemporalManager()

    class Meta:
        abstract = True
        get_latest_by = 'valid_from'
        ordering = ('-valid_from',)

    def save(self, force_insert=False, force_update=False, force_archive=True):
        """
        Saves the current instance. Subclasses which override this method
        should have the same signature.
        
        The 'force_insert' and 'force_update' parameters have the same meaning
        as for Model.save().
        The 'force_archive' parameter is used to invalidate and archive the old
        instance and then proclaim 'self' valid from now on.
        """
        # TODO: Make this method less hacky if possible
        if not (force_insert or force_update) and force_archive: 
            from datetime import date, timedelta
            today = date.today()
            self.valid_from = today
            self.valid_to = None
            try:
                # Get the latest version using unchangeable_fields as a
                # reference point
                old_instance = type(self)._default_manager.filter(
                    **dict([
                        (x, getattr(self, x)) for x in self.unchangeable_fields
                    ])
                ).latest()
                
                # Sanity check - versions shouldn't change too fast
                days_passed = (today - old_instance.valid_from).days
                if days_passed < self.min_days_between_versions:
                    raise exceptions.TemporalModelChangedTooFast(
                        old_instance,
                        days_passed,
                        self.min_days_between_versions
                    )
                
                # Sanity check - save the new version only if it is different
                # from the previous version
                changeable_fields = self.changeable_fields or get_childs_own_fields(
                    type(self),
                    TemporalModel,
                    self.unchangeable_fields
                )
                same_field_values = map(
                    lambda x: getattr(self, x)==getattr(old_instance, x),
                    changeable_fields
                )
                if all(same_field_values):
                    raise exceptions.TemporalModelHasNotChanged(old_instance)
                                
                # Old version stops being valid on yesterday to prevent the
                # overlaping with the new version
                old_instance.valid_to = today - timedelta(1)
                old_instance.save(force_insert, force_update, False)
                
                # Make Django INSERT the new instance instead of UPDATE-ing it
                self.pk = None
            except self.DoesNotExist:
                pass # There are no old instances, just ignore
        super(TemporalModel, self).save(force_insert, force_update)        
